/*
 * Data.cpp
 *
 *  Created on: Dec 18, 2013
 *      Author: thanhkm
 */

#include "Data.h"

Data::Data() {
}

Data::~Data() {
}

int Data::getClass(vector<double> signature,vector<vector<vector<double> > > listNumberDescriptors){
	double minDist = 10000, dist=10000;
	int minIdx = -1;

	for (int number= 0; number < 10; number ++ ) {
		for (uint i=0; i < listNumberDescriptors[number].size(); i++){

			//get the nearest descriptor
			dist = Utils::distance(signature, listNumberDescriptors[number][i]);
			if (dist < minDist){
				minDist = dist;
				minIdx = number;
			}
		}
	}
//std::cout << minDist << std::endl;
	return minIdx;
}

int Data::recognizeKnn(vector<double> signature,
		vector<vector<vector<double> > > listNumberDescriptors, int k){

	double dist=10000;
	int minIdx = -1;

	// get the list of distance
	std::vector<std::vector<double>> listNumberDistance;
	std::vector<double> vecDistance;
	for (int number= 0; number < 10; number ++ ) {
		for (uint i=0; i < listNumberDescriptors[number].size(); i++){
			//get the nearest descriptor
			dist = Utils::distance(signature, listNumberDescriptors[number][i]);
			vecDistance.push_back(dist);
		}
		listNumberDistance.push_back(vecDistance);
		vecDistance.clear();
	}

	// sorting
	for (int number = 0; number < 10; number++) {
		std::sort(listNumberDistance[number].begin(), listNumberDistance[number].end());
	}
	// calculate the distance of k nearest neighbor for each class
	double numberDistance[10];
	for (int number = 0; number < 10; number++) {
		numberDistance[number] = 0;
	}
	for (int number = 0; number < 10; number++) {
		for (int i = 0; i < k; i++) {
			numberDistance[number] += listNumberDistance[number][i];
		}
	}

	// get the min distance
	double minDistance = 100000;
	int minNumber = -1;
	for (int number = 0; number < 10; number++) {
		if (minDistance > numberDistance[number]){
			minDistance = numberDistance[number];
			minNumber = number;
		}
	}
std::cout << "number: " << minNumber << " total distance: " << minDistance << std::endl;
	return minNumber;
}

int Data::recognizeNearest(vector<double> signature ,
		vector<vector<vector<double> > > listNumberDescriptors, float threshold = 1.15){
	double dist=10000;
	int minIdx = -1;

	// get the list of distance
	std::vector<std::vector<double>> listNumberDistance;
	std::vector<double> vecDistance;
	for (int number= 0; number < 10; number ++ ) {
		for (uint i=0; i < listNumberDescriptors[number].size(); i++){
			//get the nearest descriptor
			dist = Utils::distance(signature, listNumberDescriptors[number][i]);
			vecDistance.push_back(dist);
		}
		listNumberDistance.push_back(vecDistance);
		vecDistance.clear();
	}

	// get the min distance
	double minDist = 1000;
	for (int number = 0; number < 10; number++) {
		for (uint i = 0; i < listNumberDistance[number].size(); i++) {
			if (listNumberDistance[number][i] < minDist) {
				minDist = listNumberDistance[number][i];
				minIdx = number;
			}
		}
	}

	// count descriptors which are near to the min distance
	int numberScore[10];
	for (int number = 0; number < 10; number++) {
		numberScore[number] = 0;
	}

	for (int number = 0; number < 10; number++) {
		for (uint i = 0; i < listNumberDistance[number].size(); i++) {
			if (listNumberDistance[number][i] <= minDist * threshold) {
				numberScore[number]++;
			}
		}
	}

	// get max Score
	int maxScore = -1;
	int maxScoreNumber = -1;
	for (int number = 0; number < 10; number++) {
		if (maxScore <= numberScore[number]){
			maxScore = numberScore[number];
			maxScoreNumber = number;
		}
	}
std::cout << "number: " << maxScoreNumber << " max score: " << maxScore << std::endl;
	return maxScoreNumber;
}

vector<vector<vector<double>>> Data::learning(int rows, int cols) {
	// c'est un liste de descripteur pour chaque chiffre
	// chaque chiffre a un liste de descripteur (10 descripteur)
	vector<vector<vector<double>>> listNumberDescriptors;

	// read training data
	vector<classImage> listTrainingImage = getData(1);

	// get descriteurs for each image
	for (uint i = 0; i < listTrainingImage.size(); i++){
		listTrainingImage[i].signature = Utils::getDescriptors(listTrainingImage[i].image, rows, cols);
//		cv::imwrite(listTrainingImage[i].nomImage, listTrainingImage[i].image);
	}

	vector<vector<double>> numberDescriptors;
	for (int number = 0; number <10; number++) {
		for (uint i = 0; i < listTrainingImage.size(); i++) {
			if (listTrainingImage[i].chiffreReel == number) {
				numberDescriptors.push_back(listTrainingImage[i].signature);
			}
		}
		listNumberDescriptors.push_back(numberDescriptors);
		numberDescriptors.clear();
	}

	return listNumberDescriptors;
}

// Programme principal
vector<classImage> Data::getData(int code) {
	// fichier contient des différentes images
	fstream fichier;
	// fichier contient des chiffres reels correspondants aux differentes images
	fstream fichier1;

	switch (code) {
	case 1:
		fichier.open("DigitData/trainingimages.txt");
		fichier1.open("DigitData/traininglabels.txt");
		break;
	case 2:
		fichier.open("DigitData/testimages.txt");
		fichier1.open("DigitData/testlabels.txt");
		break;
	case 3:
		fichier.open("DigitData/validationimages.txt");
		fichier1.open("DigitData/validationlabels.txt");
		break;
	default:
		fichier.open("DigitData/testimages.txt");
		fichier1.open("DigitData/testlabels.txt");
		break;
	}

	// Recuperation des différentes images
	vector<classImage> listImage;
	Mat recupImg = Mat::zeros(28, 28, CV_32FC1);

	vector<string> monTableau;
	classImage imgRecup;
	int k = 0;
	int nbImage = 0;
	if (!fichier)
		std::cout << "Fichier inexistant";
	else {
		while (!fichier.eof()) {
			monTableau.push_back(""); //creation d'une ligne vide

			getline(fichier, monTableau.back()); //lecture d'une ligne du fichier

			int ligne = monTableau.size() - 1; //je recupere la taille du tableau (-1 pour la ligne 0)

			if (monTableau[ligne].empty()) //si la ligne est vide
				monTableau.pop_back(); //on la retire du tableau
			else {
				const char *rep = new char[monTableau[ligne].length() + 1];
				rep = monTableau[ligne].c_str();
				for (int i = 0; i < 28; i++) {
					if (rep[i] == ' ')
						recupImg.at<float>(k, i) = 255; // Si caractere blanc mettre le pixel à 255
					else
						recupImg.at<float>(k, i) = 0;   // Sinon le mettre à 0
				}
				k++;
				if (k == 28) { // Enregistrement de chaque image
					stringstream ss;
					ss << "Img/" << nbImage << ".png";
					string filename = ss.str();
					imgRecup.image = recupImg.clone();
					imgRecup.nomImage = filename;
					//get descriptior of image
//					imgRecup.signature = Utils::getDescriptors(imgRecup.image, 14, 14);
					listImage.push_back(imgRecup);
					//uncomment for write image to harddisk
//		      		imwrite(filename, listImage[nbImage].image);
					k = 0;
					nbImage++;
				}
			}
		}
		monTableau.clear();
	}

	// Recuperation des chiffres reels correspondants aux differentes images
	vector<string> monTableau1;
	int count = 0;
	if (!fichier1)
		std::cout << "Fichier inexistant";
	else {
		while (!fichier1.eof()) {
			monTableau1.push_back(""); //creation d'une ligne vide

			getline(fichier1, monTableau1.back()); //lecture d'une ligne du fichier

			int ligne = monTableau1.size() - 1; //je recupere la taille du tableau (-1 pour la ligne 0)

			if (monTableau1[ligne].empty()) //si la ligne est vide
				monTableau1.pop_back(); //on la retire du tableau
			else {
				listImage[count].chiffreReel = atoi(monTableau1[ligne].c_str()); // Affectation du chiffre reel correspondant à chaque image
				count++;
			}
		}
		monTableau1.clear();
	}

	return listImage;
}

// Permet d'enregistrer les descripteurs dans un fichier
void Data::enregisterDescriptor(vector<vector<vector<double> > > vectDescript,
		string filename) {
	ofstream fout;
	fout.open(filename.c_str());
	for (int unsigned i = 0; i < vectDescript.size(); i++) {
		fout << "ind" << " " << i << endl;
		for (int unsigned j = 0; j < vectDescript[i].size(); j++) {
			for (int unsigned k = 0; k < vectDescript[i][j].size(); k++) {
				fout << vectDescript[i][j][k] << " ";
			}
			fout << endl;
		}
	}
	fout.close();
}

// Permet de recuperer les descripteurs
vector<vector<vector<double> > > Data::recupDescriptor(string filename) {
	vector<vector<vector<double> > > vectDescript;

	int i = 0;
	vector<string> monTableau;

	vector<double> vectJ;
	fstream fichier1(filename.c_str());
	if (!fichier1)
		std::cout << "fichier inexistant";
	else {
		while (!fichier1.eof()) {
			monTableau.push_back(""); //creation d'une ligne vide

			getline(fichier1, monTableau.back()); //lecture d'une ligne du fichier

			int ligne = monTableau.size() - 1; //je recupere la taille du tableau (-1 pour la ligne 0)

			if (monTableau[ligne].empty()) //si la ligne est vide
				monTableau.pop_back(); //on la retire du tableau
			else {
				vector<string> rep = split(monTableau[ligne], ' ');

				if (rep[0] == "ind") {
					i = atoi(rep[1].c_str());
					vector<vector<double> > vectI;
					vectDescript.push_back(vectI);
				} else {
					vector<double> vectJ;
					for (int unsigned m = 0; m < rep.size(); m++) {
						vectJ.push_back(atof(rep[m].c_str()));
					}
					vectDescript[i].push_back(vectJ);
				}
			}
		}
		monTableau.clear();
	}
	return vectDescript;
}

// Faire un split d'une chaine de caractéres en utilisant un separateur
vector<string> Data::split(string chaine, char check) {
	vector<string> tab;
	istringstream iss(chaine);
	string mot;
	while (std::getline(iss, mot, check)) {
		tab.push_back(mot);
	}
	return tab;
}

